/* 
 * Copyright (C) 1996-1998 Szeredi Miklos
 * Email: mszeredi@inf.bme.hu
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version. See the file COPYING. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* This module deals with the different tape file formats (.TAP and .TZX)  */
/* 'sptape.c' uses the functions provided by this module. */


#include "tapefile.h"
#include "tapef_p.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>

#define max(x, y) ((x) > (y) ? (x) : (y))

#define DESC_LEN 256

char seg_desc[DESC_LEN];

#define TZXMAJPROG 1
#define TZXMINPROG 2

static FILE *tapefp = NULL;

static dbyte segi, currsegi;
static int   segbeg;

static int   endtype, endnext, endplay;
static dbyte endpause;
static int finished;

static long firstseg_offs;

static struct tape_options tapeopt;

long tf_segoffs;
struct tapeinfo tf_tpi;

static dbyte loopctr, loopbeg;
static dbyte callctr, callbeg;

#define ST_NORM  0
#define ST_PSEQ  1
#define ST_DIRE  2
#define ST_MISC  3

#define PL_NONE    0
#define PL_PAUSE   1
#define PL_LEADER  2
#define PL_DATA    3
#define PL_END     4
#define PL_PSEQ    5
#define PL_DIRE    6

#define IMP_1MS   3500

static dbyte lead_pause;
static int playstate = PL_NONE;
static int currlev;

#define DEF_LEAD_PAUSE 2000

struct seginfo tf_cseg;

struct tzxblock {
  int type;
  int lenbytes;
  int lenmul;
  int hlen;        
};

#define NUMBLOCKID 0x60
#define NONE 0
#define COMM 1
#define STAN 2


#define RBUFLEN 1024

static byte rbuf[RBUFLEN];

/* Table containing information on TZX blocks */

static struct tzxblock tzxb[NUMBLOCKID] = {
  { NONE },                    /* ID: 00 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { NONE },                    /* ID: 08 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { COMM, 2, 1, 0x04 },        /* ID: 10 */
  { COMM, 3, 1, 0x12 },
  { COMM, 0, 1, 0x04 },
  { COMM, 1, 2, 0x01 },
  { COMM, 3, 1, 0x0A },
  { COMM, 3, 1, 0x08 },
  { NONE },
  { NONE },

  { NONE },                    /* ID: 18 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { COMM, 0, 1, 0x02 },        /* ID: 20 */
  { COMM, 1, 1, 0x01 },
  { COMM, 0, 1, 0x00 },
  { COMM, 0, 1, 0x02 },
  { COMM, 0, 1, 0x02 },
  { COMM, 0, 1, 0x00 },
  { COMM, 2, 2, 0x02 },
  { COMM, 0, 1, 0x00 },

  { COMM, 2, 1, 0x02 },        /* ID: 28 */
  { NONE },
  { STAN, 0, 1, 0x00 },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { COMM, 1, 1, 0x01 },        /* ID: 30 */
  { COMM, 1, 1, 0x02 },
  { COMM, 2, 1, 0x02 },
  { COMM, 1, 3, 0x01 },
  { COMM, 0, 1, 0x08 },
  { COMM, 4, 1, 0x14 },
  { NONE },
  { NONE },

  { NONE },                    /* ID: 38 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { COMM, 3, 1, 0x04 },        /* ID: 40 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { NONE },                    /* ID: 48 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { NONE },                    /* ID: 50 */
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE },

  { NONE },                    /* ID: 58 */
  { NONE },
  { COMM, 0, 1, 0x09 },
  { NONE },
  { NONE },
  { NONE },
  { NONE },
  { NONE }
};


#define PTRDIFF(pe, ps) ((int) (((long) (pe) - (long) (ps)) / sizeof(*pe)))

static char tzxheader[] = {'Z','X','T','a','p','e','!',0x1A};

static int readbuf(void *ptr, int size, FILE *fp)
{
  return (int) fread(ptr, 1, (size_t) size, tapefp);
}

static void premature(struct seginfo *csp)
{
  csp->segtype = SEG_ERROR;
  sprintf(seg_desc, "Premature end of segment");
}

static int read_tzx_header(byte *hb, struct seginfo *csp)
{
  int res;  
  int segid, seght;
  int lenoffs, lenbytes, lenmul, lenadd;
  int hlen;
  long length;
  byte *hip;
  
  segid = getc(tapefp);
  if(segid == EOF) {
    csp->segtype = SEG_END;
    sprintf(seg_desc, "End of Tape");
    return 0;
  }
  
  hb[0] = (byte) segid;
  
  if(segid < NUMBLOCKID) seght = tzxb[segid].type;
  else seght = NONE;
  
  if(seght == COMM) {
    lenbytes = tzxb[segid].lenbytes;
    lenmul   = tzxb[segid].lenmul;
    hlen     = tzxb[segid].hlen;
    lenadd   = hlen;
    lenoffs  = hlen - lenbytes;
  }
  else {
    lenoffs = 0x00;
    lenbytes = 4;
    lenmul = 1;
    lenadd = 0x00;
    hlen = 0x04;
  }
  
  if(seght == STAN) hlen += tzxb[segid].hlen;

  hip = hb+1;
  res = readbuf(hip, hlen, tapefp);
  if(res != hlen) {
    premature(csp);
    return 0;
  }
  length = 0;
  for(;lenbytes; lenbytes--) 
    length = (length << 8) + hip[lenoffs + lenbytes - 1];
  
  length = (length * lenmul) + lenadd - hlen;
  
  csp->len = length;
  return 1;
}

static int read_tap_header(byte *hb, struct seginfo *csp)
{
  int res;

  res = readbuf(hb, 2, tapefp);
  if(res < 2) {
    if(res == 0) {
      csp->segtype = SEG_END;
      sprintf(seg_desc, "End of Tape");
    }
    else premature(csp);
    return 0;
  }
  csp->len = DBYTE(hb, 0);
  return 1;
}

static int t_read_header(byte *hb, struct seginfo *csp)
{
  segbeg = 0;
  csp->ptr = 0;

  csp->segtype = SEG_OTHER;
  if(tf_tpi.type == TAP_TAP) 
    return read_tap_header(hb, csp);
  else if(tf_tpi.type == TAP_TZX) 
    return read_tzx_header(hb, csp);

  return 0;
}

static void isbeg(void)
{
  segbeg = 1;
  tf_cseg.len = tf_cseg.ptr = 0;
}


static int end_seg(struct seginfo *csp)
{
  if(!segbeg) {
    if(csp->len != csp->ptr) {
      fseek(tapefp, tf_cseg.len - tf_cseg.ptr - 1, SEEK_CUR);

      if(getc(tapefp) == EOF) {
	premature(csp);
	return 0;
      }
    }
    segi++;
    isbeg();
  }
  playstate = PL_NONE;
  return 1;
}


static int jump_to_segment(int newsegi, struct seginfo *csp)
{
  if(newsegi <= segi) {
    segi = 0;
    isbeg();
    fseek(tapefp, firstseg_offs, SEEK_SET);
  }
  else if(!end_seg(csp)) return 0;

  while(segi != newsegi) {
    if(!t_read_header(rbuf, csp)) return 0;
    if(!end_seg(csp)) return 0;
  }
  return 1;
}


static int next_data(void)
{
  int res;
  if(tf_cseg.ptr == tf_cseg.len) return DAT_END;
  
  res = getc(tapefp);
  if(res == EOF) {
    sprintf(seg_desc, "Premature end of segment");
    return DAT_ERR;
  }
  tf_cseg.ptr++;
  return res;
}


static void normal_segment(struct seginfo *csp)
{
  sprintf(seg_desc, "Data");
  csp->type    = ST_NORM;
  csp->segtype = SEG_DATA;
  csp->pulse   = 2168;   /* 2016 */
  csp->num     = 3220;
  csp->sync1p  = 667;
  csp->sync2p  = 735;
  csp->zerop   = 855;   /* 672 */
  csp->onep    = 1710;  /* 1568 */
  csp->bused   = 8;
}


static int interpret_tzx_header(byte *hb, struct seginfo *csp)
{
  int res;
  int segid;
  byte *hip;
  int offs;
  dbyte dtmp;

  segid = hb[0];
  hip = hb+1;
  
  switch(segid) {
  case 0x10:
    normal_segment(csp);
    csp->pause   = DBYTE(hip, 0x00);
    break;
    
  case 0x11:
    sprintf(seg_desc, "Turbo Data");
    csp->type    = ST_NORM;
    csp->segtype = SEG_DATA_TURBO;
    csp->pulse   = DBYTE(hip, 0x00);
    csp->sync1p  = DBYTE(hip, 0x02);
    csp->sync2p  = DBYTE(hip, 0x04);
    csp->zerop   = DBYTE(hip, 0x06);
    csp->onep    = DBYTE(hip, 0x08);
    csp->num     = DBYTE(hip, 0x0A);
    csp->bused   = BYTE(hip, 0x0C);
    csp->pause   = DBYTE(hip, 0x0D);
    break;
    
  case 0x12:
    sprintf(seg_desc, "Pure Tone");
    csp->type    = ST_NORM;
    csp->segtype = SEG_OTHER;
    csp->pulse   = DBYTE(hip, 0x00);
    csp->num     = DBYTE(hip, 0x02);
    csp->sync1p  = 0;
    csp->sync2p  = 0;
    csp->zerop   = 0;
    csp->onep    = 0;
    csp->bused   = 0;
    csp->pause   = 0;
    break;
    
  case 0x13:
    sprintf(seg_desc, "Pulse Sequence");
    csp->type    = ST_PSEQ;
    csp->segtype = SEG_OTHER;
    csp->pause   = 0;
    break;
    
  case 0x14:
    sprintf(seg_desc, "Pure Data");
    csp->type    = ST_NORM;
    csp->segtype = SEG_DATA_PURE;
    csp->zerop   = DBYTE(hip, 0x00);
    csp->onep    = DBYTE(hip, 0x02);
    csp->bused   = BYTE(hip, 0x04);
    csp->pause   = DBYTE(hip, 0x05);
    csp->pulse   = 0;
    csp->num     = 0;
    csp->sync1p  = 0;
    csp->sync2p  = 0;
    break;
    
  case 0x15:
    sprintf(seg_desc, "Direct Recording");
    csp->type    = ST_DIRE;
    csp->segtype = SEG_OTHER; 
    csp->pulse   = DBYTE(hip, 0x00);
    csp->pause   = DBYTE(hip, 0x02);
    csp->bused   = BYTE(hip, 0x04);
    break;
    
  case 0x20:
    dtmp = DBYTE(hip, 0x00);
    if(dtmp == 0) {
      if(!tapeopt.stoppause) {
	csp->type = ST_MISC;
	csp->segtype = SEG_STOP;
      }
      else {
	csp->pause = tapeopt.stoppause * 1000;
	csp->type = ST_NORM;
	csp->segtype = SEG_PAUSE;
      }
      sprintf(seg_desc, "Stop the Tape Mark");
    }
    else {
      csp->pause = dtmp;
      csp->type = ST_NORM;
      csp->segtype = SEG_PAUSE;
      sprintf(seg_desc, "Pause for %i.%03is", 
	      csp->pause / 1000, csp->pause % 1000);
    }
    csp->pulse   = 0;
    csp->num     = 0;
    csp->sync1p  = 0;
    csp->sync2p  = 0;
    csp->zerop   = 0;
    csp->onep    = 0;
    csp->bused   = 0;
    break;
    
  case 0x21:
    csp->type    = ST_MISC;
    csp->segtype = SEG_GRP_BEG;
    res = readbuf(rbuf, csp->len, tapefp);
    if(res != (int) csp->len) {
      premature(csp);
      return 0;
    }
    csp->ptr += csp->len;
    {
      int blen;
      sprintf(seg_desc, "Begin Group: ");
      blen = (int) strlen(seg_desc);
      strncpy(seg_desc+blen, (char *) rbuf, (unsigned) csp->len);
      seg_desc[csp->len + blen] = '\0';
    }
    break;

  case 0x22:
    sprintf(seg_desc, "End Group");
    csp->type    = ST_MISC;
    csp->segtype = SEG_GRP_END;
    break;

  case 0x23:
    offs = (signed short) DBYTE(hip, 0x00);
    if(offs == 0) {
      sprintf(seg_desc, "Infinite loop");
      csp->type = ST_MISC;
      csp->segtype = SEG_STOP;
    }
    else {
      csp->type = ST_MISC;
      csp->segtype = SEG_SKIP;
      sprintf(seg_desc, "Jump to %i", segi+offs);
      jump_to_segment(segi + offs, csp);
    }
    break;

  case 0x24:
    loopctr = DBYTE(hip, 0x00);
    sprintf(seg_desc, "Loop %i times", loopctr);
    loopbeg = segi+1;
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    break;

  case 0x25:
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    if(loopctr) loopctr--;
    if(loopctr) {
      jump_to_segment(loopbeg, csp);
      sprintf(seg_desc, "Loop to: %i", loopbeg);
    }
    else sprintf(seg_desc, "Loop End");
    break;

  case 0x26:
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    dtmp = DBYTE(hip, 0x00);
    if(callctr < dtmp) {
      int offset;
      callbeg = segi;
      fseek(tapefp, callctr*2, SEEK_CUR);
      csp->ptr += callctr*2;
      res = readbuf(rbuf, 2, tapefp);
      if(res != 2) {
	premature(csp);
	return 0;
      }
      csp->ptr += 2;
      offset = (signed short) DBYTE(rbuf, 0x00);
      sprintf(seg_desc, "Call to %i", segi+offset);
      jump_to_segment(segi+offset, csp);
      callctr++;
    }
    else {
      callctr = 0;
      sprintf(seg_desc, "Call Sequence End");
    }
    break;

  case 0x27:
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    sprintf(seg_desc, "Return");
    if(callctr > 0) jump_to_segment(callbeg, csp);
    break;

  case 0x28:
    sprintf(seg_desc, "Selection (Not yet supported)");
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    break;

  case 0x2A:
    if(tapeopt.machine == MACHINE_48) { 
      sprintf(seg_desc, "Stop the Tape in 48k Mode (Stopped)");
      csp->type = ST_MISC;
      csp->segtype = SEG_STOP;
    }
    else {
      sprintf(seg_desc, "Stop the Tape in 48k Mode (Not Stopped)");
      csp->type = ST_MISC;
      csp->segtype = SEG_SKIP;
    }
    break;

  case 0x31:
  case 0x30:
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    res = readbuf(rbuf, csp->len, tapefp);
    if(res != (int) csp->len) {
      premature(csp);
      return 0;
    }
    csp->ptr += csp->len;
    strncpy(seg_desc, (char *) rbuf, (unsigned) csp->len);
    seg_desc[csp->len] = '\0';
    break;

  case 0x32:
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    {
      int numstr, i;
      
      i = 0;
      numstr = next_data();
      for(;numstr > 0; numstr--) {
	int tlen, tid, b;

	tid = next_data();
	tlen = next_data();
	if(tid < 0 || tlen < 0) return 0;
	
	for(; tlen; tlen--) {
	  b = next_data();
	  if(b < 0) return 0;
	  seg_desc[i++] = b;
	}
	seg_desc[i++] = '\n';
      }
      seg_desc[i] = '\0';
    }
    break;
    
  case 0x33:
    sprintf(seg_desc, "Hardware Information (Not yet supported)");
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    break;

  case 0x34:
    sprintf(seg_desc, "Emulation Information (Not yet supported)");
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    break;

  case 0x35:
    sprintf(seg_desc, "Custom Information (Not yet supported)");
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    break;

  case 0x40:
    sprintf(seg_desc, "Snapshot (Not yet supported)");
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    break;

  case 0x5A:
    sprintf(seg_desc, "Tapefile Concatenation Point");
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    
  default:
    csp->type    = ST_MISC;
    csp->segtype = SEG_SKIP;
    sprintf(seg_desc, "Unknown TZX block (id: %02X, version: %i.%02i)", 
	    segid, tf_tpi.tzxmajver, tf_tpi.tzxminver);
    break;
  }

  return 1;
}

static int interpret_header(byte *hb, struct seginfo *csp)
{
  if(tf_tpi.type == TAP_TAP) {
    normal_segment(csp);
    csp->pause   = DEF_LEAD_PAUSE;

    return 1;
  }
  else if(tf_tpi.type == TAP_TZX) 
    return interpret_tzx_header(hb, csp);

  return 0;
}  

byte *tf_get_block(int i)
{
  seg_desc[0] = '\0';

  if(jump_to_segment(i, &tf_cseg)) {
    tf_segoffs = ftell(tapefp);

    if(t_read_header(rbuf, &tf_cseg) && 
       interpret_header(rbuf, &tf_cseg)) return rbuf;
  }
  return NULL;
}


int next_byte(void)
{
  playstate = PL_NONE;
  return next_data();
}

#define DPULSE(v1,v2) (*impbuf++=(v1), *impbuf++=(v2), timelen-=(v1)+(v2))
#define PULSE(v) (*impbuf++=(v), currlev = !currlev, timelen-=(v))

int next_imps(unsigned short *impbuf, int buflen, long timelen)
{
  static int toput;
  static int bitrem;
  static dbyte dirpulse;
  unsigned short *impbufend, *impbufstart;
  
  impbufstart = impbuf;
  impbufend = impbuf + buflen;
  
  while(impbuf < impbufend - 1 && timelen > 0) {
    switch(playstate) {
      
    case PL_PAUSE:
      if(currlev && lead_pause) {
	PULSE(IMP_1MS);
	lead_pause --;
      }
      else if(lead_pause > 10) {
	if(tapeopt.blanknoise && !(rand() % 64)) 
	  DPULSE(IMP_1MS * 10 - 1000, 1000); 
	else 
	  DPULSE(IMP_1MS * 10, 0);
	lead_pause -= 10;
      }
      else if(lead_pause) {
	DPULSE(IMP_1MS, 0);
	lead_pause --;
      }
      else {
	if(tf_cseg.num || tf_cseg.sync1p || tf_cseg.sync2p ||
	   tf_cseg.ptr != tf_cseg.len) finished = 0;

	switch (tf_cseg.type) {
	case ST_NORM: playstate = PL_LEADER; break;
	case ST_DIRE: playstate = PL_DIRE;   dirpulse = 0; break;
	case ST_PSEQ: playstate = PL_PSEQ;   break;
	default:      playstate = PL_NONE;
	}
      }
      break;

    case PL_LEADER:
      if(tf_cseg.num >= 2) {
	DPULSE(tf_cseg.pulse, tf_cseg.pulse);
	tf_cseg.num -= 2;
      }
      else
      if(tf_cseg.num) {
	PULSE(tf_cseg.pulse);
	tf_cseg.num --;
      }
      else { /* PL_SYNC */
	if(tf_cseg.sync1p || tf_cseg.sync2p) 
	  DPULSE(tf_cseg.sync1p, tf_cseg.sync2p);
	bitrem = 0;
	playstate = PL_DATA;
      }
      break;
      
    case PL_DATA:
      if(!bitrem) {
	toput = next_data();
	if(toput < 0) {
	  playstate = PL_END;
	  break;
	}
	if(tf_cseg.ptr != tf_cseg.len) {
	  if(timelen > 16 * max(tf_cseg.onep, tf_cseg.zerop) && 
	     impbuf <= impbufend - 16) {
	    int p1, p2, br, tp;
	    
	    p1 = tf_cseg.onep;
	    p2 = tf_cseg.zerop;
	    br = 8;
	    tp = toput;
	    
	    while(br) {
	      if(tp & 0x80) DPULSE(p1, p1);
	      else          DPULSE(p2, p2);
	      br--;
	      tp <<= 1;
	    }
	    bitrem = 0;
	    break;
	  }
	  bitrem = 8;
	}
	else {
	  bitrem = tf_cseg.bused;
	  if(!bitrem) break;
	}
      }
      if(toput & 0x80) DPULSE(tf_cseg.onep, tf_cseg.onep);
      else             DPULSE(tf_cseg.zerop, tf_cseg.zerop);
      bitrem--, toput <<= 1;
      break;

    case PL_PSEQ:
      {
	int b1, b2;
	dbyte pulse1, pulse2;
	b1 = next_data();
	b2 = next_data();
	if(b1 < 0 || b2 < 0) {
	  playstate = PL_END;
	  break;
	}
	pulse1 = b1 + (b2 << 8);

	b1 = next_data();
	b2 = next_data();
	if(b1 < 0 || b2 < 0) {
	  PULSE(pulse1);
	  playstate = PL_END;
	  break;
	}
	pulse2 = b1 + (b2 << 8);
	DPULSE(pulse1, pulse2);
      }
      break;

    case PL_DIRE:
      for(;;) {
	if(!bitrem) {
	  toput = next_data();
	  if(toput < 0) {
	    playstate = PL_END;
	    DPULSE(dirpulse, 0);
	    break;
	  }
	  if(tf_cseg.ptr != tf_cseg.len) bitrem = 8;
	  else {
	    bitrem = tf_cseg.bused;
	    if(!bitrem) break;
	  }
	}
	bitrem--;
	toput <<= 1;
	if(((toput & 0x0100) ^ (currlev ? 0x0100 : 0x00))) {
	  PULSE(dirpulse);
	  dirpulse = tf_cseg.pulse;
	  break;
	}
	dirpulse += tf_cseg.pulse;
	if(dirpulse >= 0x8000) {
	  DPULSE(dirpulse, 0);
	  dirpulse = 0;
	  break;
	}
      }
      break;

    case PL_END:
      if(tf_cseg.pause) {
	PULSE(IMP_1MS);
	tf_cseg.pause--;
	if(currlev) PULSE(0);
	finished = 1;
      }
      playstate = PL_NONE;
      break;
      
    case PL_NONE:
    default:
      return PTRDIFF(impbuf, impbufstart);
    }
  }

  return PTRDIFF(impbuf, impbufstart);
}


int next_segment(void)
{
  if(endnext) {
    endnext = 0;
    tf_cseg.segtype = endtype;
    tf_cseg.pause = endpause;
    playstate = endplay;
    return tf_cseg.segtype;
  }

  seg_desc[0] = '\0';
  lead_pause = tf_cseg.pause;
  
  if(end_seg(&tf_cseg)) {
    currsegi = segi;
    if(t_read_header(rbuf, &tf_cseg)) interpret_header(rbuf, &tf_cseg);
  }

  if(tf_cseg.segtype >= SEG_DATA) {
    playstate = PL_PAUSE;
    if(lead_pause) finished = 1;
  }
  else playstate = PL_NONE;
  
  if(tf_cseg.segtype <= SEG_STOP && !finished) {
    endnext = 1;
    endtype = tf_cseg.segtype;
    endpause = tf_cseg.pause;
    endplay  = playstate;
    if(lead_pause > 0) lead_pause--;

    tf_cseg.pause = 1;
    tf_cseg.segtype = SEG_VIRTUAL;
    playstate = PL_END;
  }

  return tf_cseg.segtype;
}

int goto_segment(int at_seg)
{
  int res;

  res = jump_to_segment(at_seg, &tf_cseg);
  tf_cseg.pause = DEF_LEAD_PAUSE;

  return res;
}

unsigned segment_pos(void)
{
  return currsegi;
}

void close_tapefile(void)
{
  if(tapefp != NULL) {
    playstate = PL_NONE;
    fclose(tapefp);
    tapefp = NULL;
  }
}

int open_tapefile(char *name, int type)
{
  int res;
  int ok;

  seg_desc[0] = '\0';
  currlev = 0;

  if(type != TAP_TAP && type != TAP_TZX) {
    sprintf(seg_desc, "Illegal tape type");
    return 0;
  }

  tapefp = fopen(name, "rb");
  if(tapefp == NULL) {
    sprintf(seg_desc, "Could not open `%s': %s", name, strerror(errno));
    return 0;
  }
  
  tf_tpi.type = type;
  tf_cseg.pause = DEF_LEAD_PAUSE;
  INITTAPEOPT(tapeopt);

  currsegi = segi = 0;
  isbeg();

  firstseg_offs = 0;
  
  ok = 1;
  
  if(tf_tpi.type == TAP_TZX) {
    
    firstseg_offs = 10;
    res = readbuf(rbuf, 10, tapefp);
    if(res == 10 && strncmp((char *)rbuf, tzxheader, 8) == 0) {
      tf_tpi.tzxmajver = rbuf[8];
      tf_tpi.tzxminver = rbuf[9];
      
      if(tf_tpi.tzxmajver > TZXMAJPROG) {
	sprintf(seg_desc, 
		"Cannot handle TZX file version (%i.%02i)", 
		tf_tpi.tzxmajver, tf_tpi.tzxminver);
	ok = 0;
      }
    }
    else {
      sprintf(seg_desc, "Illegal TZX file header");
      ok = 0;
    }
  }
  
  if(!ok) {
    close_tapefile();
    return 0;
  }
  endnext = 0;

  loopctr = 0;
  callctr = 0;

  return 1;
}

int get_level(void)
{
  return currlev;
}

long get_seglen(void)
{
  return tf_cseg.len;
}

long get_segpos(void)
{
  return tf_cseg.ptr;
}

void set_tapefile_options(struct tape_options *to)
{
  memcpy(&tapeopt, to, sizeof(tapeopt));
}

